package org.infodavid.common.persistence.impl;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.PersistenceException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.infodavid.common.persistence.IDataObject;
import org.infodavid.common.persistence.IDataService;
import org.infodavid.common.persistence.ILockable;
import org.infodavid.common.persistence.IPersistentData;
import org.infodavid.common.persistence.PersistenceSession;
import org.infodavid.common.persistence.exceptions.IErrorHandler;
import org.infodavid.common.reflect.MethodUtil;

/**
 * The Class AbstractDataService.
 * @param <T> the generic type
 * @param <K> the key type
 */
public abstract class AbstractDataService<T extends IDataObject<K>,K extends Serializable>
    implements IDataService<T,K> {
  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(AbstractDataService.class);

  /** The Constant DEFAULT_IMMUTABLE_FIELDS. */
  protected static final String[] DEFAULT_IMMUTABLE_FIELDS = {
      IDataObject.KEY, IDataObject.CREATION_DATE
  };

  /**
   * Check unique.
   * @param session the session
   * @param clazz the clazz
   * @param queryName the query name
   * @return the t
   * @throws PersistenceException the persistence exception
   */
  public Object executeQuery(final PersistenceSession session, final Class<T> clazz,
      final String queryName) throws PersistenceException {
    return session.executeQuery(clazz, queryName, null, true);
  }

  /** The error handler. */
  private IErrorHandler<PersistenceException> errorHandler = new DefaultErrorHandler();

  /**
   * Instantiates a new helper.
   */
  protected AbstractDataService() {
    super();
  }

  /**
   * Gets the error handler.
   * @return the errorHandler
   */
  @Override
  public IErrorHandler<PersistenceException> getErrorHandler() {
    return errorHandler;
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#getImmutableFields()
   */
  @Override
  public String[] getImmutableFields() {
    return DEFAULT_IMMUTABLE_FIELDS;
  }

  /**
   * Handle.
   * @param type the type
   * @param id the id
   * @param error the error
   * @throws PersistenceException the persistence exception
   */
  @SuppressWarnings("rawtypes")
  protected void handle(final Class type, final Object id, final Throwable error)
      throws PersistenceException {
    if (error instanceof PersistenceException) {
      throw (PersistenceException)error;
    }

    if (errorHandler == null) {
      throw new PersistenceException(error.getMessage());
    }

    errorHandler.handle(type, id, error);
  }

  /**
   * Sets the error handler.
   * @param value the error handler
   */
  @Override
  public void setErrorHandler(final IErrorHandler<PersistenceException> value) {
    errorHandler = value;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#update(org.infodavid.common.persistence.
   * PersistenceSession, java.util.Collection)
   */
  @Override
  public void update(final PersistenceSession session, final Collection<T> collection)
      throws PersistenceException {
    session.updateEntities(collection, null);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#update(org.infodavid.common.persistence.
   * PersistenceSession, org.infodavid.common.model.IDataObject)
   */
  @Override
  public void update(final PersistenceSession session, final T value) throws PersistenceException {
    session.updateEntity(value);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#insert(org.infodavid.common.persistence.
   * PersistenceSession, java.util.Collection)
   */
  @Override
  public void insert(final PersistenceSession session, final Collection<T> collection)
      throws PersistenceException {
    session.insertEntities(collection, null);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#insert(org.infodavid.common.persistence.
   * PersistenceSession, org.infodavid.common.model.IDataObject)
   */
  @Override
  public void insert(final PersistenceSession session, final T value) throws PersistenceException {
    session.insertEntity(value);
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#delete(org.infodavid.common.persistence.PersistenceSession,
   *      java.io.Serializable)
   */
  @Override
  public void delete(final PersistenceSession session, final K key) throws PersistenceException {
    session.remove(getEntityClass(), key);
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#findByKey(org.infodavid.common.persistence.PersistenceSession,
   *      java.io.Serializable)
   */
  @Override
  public T findByKey(final PersistenceSession session, final K key) throws PersistenceException {
    return session.findEntity(getEntityClass(), key);
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#findByKey(org.infodavid.common.persistence.PersistenceSession,
   *      java.io.Serializable, org.infodavid.common.persistence.ILockable.ELockType)
   */
  @Override
  public T findByKey(final PersistenceSession session, final K key,
      final ILockable.ELockType lockType) throws PersistenceException {
    return session.findEntity(getEntityClass(), key, lockType);
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#merge(org.infodavid.common.model.IDataObject,
   * org.infodavid.common.model.IDataObject)
   */
  @Override
  public void merge(final T partial, final T target) throws PersistenceException {
    final Set<String> gettersToIgnore = new HashSet<String>();

    for (final String item : getImmutableFields()) {
      gettersToIgnore.add("get" + item);
      gettersToIgnore.add("is" + item);
    }

    try {
      MethodUtil.getInstance().merge(partial, target, gettersToIgnore);
    }
    catch (final Exception e) {
      handle(getEntityClass(), target.getKey(), e);
    }
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#delete(org.infodavid.common.persistence.PersistenceSession,
   *      java.util.Collection)
   */
  @Override
  public <L extends IPersistentData<K>> void delete(PersistenceSession session,
      Collection<L> collection) throws PersistenceException {
    if (collection == null || collection.isEmpty()) {
      LOGGER.debug("Can't delete entities, no collection specified");
    }
    else {
      for (L item : collection) {
        session.remove(getEntityClass(), item.getKey());
      }
    }
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#deleteByKeys(org.infodavid.common.persistence.PersistenceSession,
   *      java.util.Collection)
   */
  @Override
  public void deleteByKeys(PersistenceSession session, Collection<K> keys)
      throws PersistenceException {
    if (keys == null || keys.isEmpty()) {
      LOGGER.debug("Can't delete entities, no key specified");
    }
    else {
      for (K key : keys) {
        session.remove(getEntityClass(), key);
      }
    }
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#delete(org.infodavid.common.persistence.PersistenceSession,
   *      org.infodavid.common.persistence.IPersistentData)
   */
  @Override
  public <L extends IPersistentData<K>> void delete(PersistenceSession session, L value)
      throws PersistenceException {
    if (value == null) {
      LOGGER.debug("Can't delete entity, no entity specified");
    }
    else {
      session.remove(getEntityClass(), value.getKey());
    }
  }
}
